#pragma once

#include <iostream>
#include <string>
#include "Log.hpp"
#include "Socket.hpp"
#include <memory>
#include <sys/select.h>

using namespace SocketModule;
using namespace LogModule;

#define NUM sizeof(fd_set)*8 // FD_SETSIZE使用系统定义的最大fd数量
const int gdefaultfd = -1;

// 最开始的时候，tcpserver只有一个listensockfd
class SelectServer
{
public:
    SelectServer(int port)
    : _port(port),
     _listen_socket(std::make_unique<TcpSocket>()),
     _isrunning(false)
    {
    }

    void Init()
    {
        _listen_socket->BuildTcpSocketMethod(_port);
        // 初始化时将所有位置设为无效值
        for(int i = 0; i < NUM; i++)
        {
            _fd_array[i] = gdefaultfd;
        }

        // 添加到 listensockfd 到 fd_array 中
        _fd_array[0] = _listen_socket->Fd();// 将监听套接字放在固定位置（如索引0）
    }

    void Loop()
    {
        fd_set rfds; // 读文件描述符集
        _isrunning = true;
        while(_isrunning)
        {
            // 1. 清空 fd（初始化）
            FD_ZERO(&rfds);
            struct timeval timeout = {10, 0};
            int maxfd = gdefaultfd;

            for(int i = 0; i < NUM; i++)
            {
                if(_fd_array[i] == gdefaultfd) continue;
                // 2. 将 合法fd 添加到 rfds 中
                FD_SET(_fd_array[i], &rfds);// 放循环体，因为每次都要重复设置，包含 listensockfd
                // 3. 记录最大的 fd
                if(_fd_array[i] > maxfd) maxfd = _fd_array[i];
            }
            
            // 3. 不能让 accepet 来阻塞检测新连接到来，应该用 select 负责进行就绪事件的检测
                // 用户告诉内核，你要帮我关心 &rfds 中的哪些 fd 有事件就绪，然后内核帮你处理
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);
            switch (n)
            {
            case 0:
                std::cout << "time out..." << std::endl; 
                break;
            case -1:
                perror("select");
                break;
            default:
                // 有事件就绪
                // rfds: 内核告诉用户，关心 rfds 中的哪些 fd 有事件就绪
                std::cout << "有事件就绪... , timeout: "<< timeout.tv_sec << "s " << timeout.tv_usec << "us" << std::endl;
                Dispatcher(rfds); // 把已经就绪的sockfd，派发给指定的模块
                break;
            } 
        }
        _isrunning = false;
    }

    void Accepter() // 连接获取
    {
        // 有新连接到来 1. 获取新连接
        InetAddr client;
        int newfd = _listen_socket->Accepter(&client); // 这里就不会被阻塞了，因为这里 select 已经告诉我们 listensockfd 就绪了，只需要执行拷贝即可 
        
        // 2. 处理新连接
        if(newfd < 0) return ;
        else
        {
            std::cout << "获得了应该新的连接: " << newfd << " client info: " << client.Addr() << std::endl;
            // recv() 读事件是否就绪, 我们并不清楚! newfd 托管给select，select 帮我进行关心新的 sockfd 上面的读事件就绪
            // 怎么把新的 newfd 托管给 select，让 select 帮我去关心newfd 上的读事件 --> +到辅助数组
            int pos = -1;
            for(int j = 0; j < NUM; j++)
            {
                if(_fd_array[j] == gdefaultfd){
                    pos = j; // 找到一个暂未初始化的 
                    break;
                }
            }

            if(pos == -1){
                LOG(LogLevel::ERROR) << "服务器已经满了....";
                close(newfd);
            }else{
                _fd_array[pos] = newfd;
            }
        }
    }

    void Recver(int i)
    {
        // 合法的 就绪的 fd
        // 这里的 recv 不对 不完善 --> 必须要有协议
        char buf[1024];
        ssize_t n = recv(_fd_array[i], buf, sizeof(buf) - 1, 0); // 会不会阻塞，就绪了
        if(n > 0)
        {
            buf[n] = 0;
            std::cout << "client# " << buf << std::endl;
            // 把读到的信息 回显回去
            std::string message = "echo# ";
            message += buf;
            send(_fd_array[i], message.c_str(), message.size(), 0);
        }
        else if(n == 0)
        { 
            LOG(LogLevel::DEBUG) << "客户端退出, sockfd: " << _fd_array[i];
            close(_fd_array[i]);
            _fd_array[i] = gdefaultfd;
        }
        else
        {
            LOG(LogLevel::DEBUG) << "客户端读取错误, sockfd: " << _fd_array[i] ;
            close(_fd_array[i]);
            _fd_array[i] = gdefaultfd;
        }
    }

    void Dispatcher(fd_set& rfds) // rfds 就可能会有更多的 fd 就绪，不仅仅是 listenfd 就绪
    {
        for(int i = 0; i < NUM; i++)
        {
            if(_fd_array[i] == gdefaultfd) continue;
            // 文件描述符先得是合法的 
            
            if(_fd_array[i] == _listen_socket->Fd()){
                if (FD_ISSET(_fd_array[i], &rfds)) Accepter();
            }
            else{
                if(FD_ISSET(_fd_array[i], &rfds)) Recver(i);
            }
        }   
    }

    ~SelectServer()
    {}

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listen_socket;
    bool _isrunning;
    int _fd_array[NUM]; // 辅助数组
};